/*
 * YahooPipe Adapter. Converts a pipe's JSON representation into a data mashup canonical model
 * Copyright (C) 2011 Carlos Rodriguez
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package eu.ict_omelette.mashup.adapters.pipes;

import eu.ict_omelette.mashup.adapters.pipes.database.DatabaseParser;
import eu.ict_omelette.mashup.canonicalmodel.CanonicalMashup;
import eu.ict_omelette.mashup.canonicalmodel.Component;
import eu.ict_omelette.mashup.canonicalmodel.Dataflow;
import eu.ict_omelette.mashup.canonicalmodel.GlobalParameter;
import eu.ict_omelette.mashup.canonicalmodel.InputParameter;
import eu.ict_omelette.mashup.canonicalmodel.InputPort;
import eu.ict_omelette.mashup.canonicalmodel.OutputPort;
import eu.ict_omelette.mashup.transformer.api.AdapterPlugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author carlos
 */
public class PipesAdapter implements AdapterPlugin{
    public static final String TRUE = "true";
    public static final String FALSE = "false";

    private CanonicalMashup canMashup = new CanonicalMashup();

    public void loadSourceMashupModel(URI uri) throws MalformedURLException{
        File file = new File(uri);

        try {

            BufferedReader reader = new BufferedReader(new FileReader(file));

            canMashup.setId(uri.getPath());

            Pipe pipe = new Pipe(reader);            

            /***************************************************
             * Here we load the modules (components) of the Pipe
             ***************************************************/
            LinkedList modules = pipe.getModules();
            Iterator moduleIt = modules.iterator();
            while(moduleIt.hasNext()){
                Map module = (Map)moduleIt.next();
                Component comp = processModule(module);               
                this.canMashup.addComponent(comp);
            }


            /******************************************************
             * Here we load the wires (dataflow) between components
             ******************************************************/
            LinkedList wires = pipe.getWires();

            if(wires != null){
                Iterator wiresIt = wires.iterator();
                while(wiresIt.hasNext()){
                    Map wire = (Map)wiresIt.next();

                    //When a pipe does not have wires, for some reason, we still get
                    //until this part the code. If we do not check the that wire==null,
                    //we get a runtime exception
                    if(wire == null)
                        continue;

                    String from = wire.get("src").toString();
                    String to = wire.get("tgt").toString();

                    from = from.replaceAll("\\{", "");
                    from = from.replaceAll("\\}", "");
                    from = from.replaceAll(" ", "");

                    to = to.replaceAll("\\{", "");
                    to = to.replaceAll("\\}", "");
                    to = to.replaceAll(" ", "");

                    String fromPair[]   = from.split(",");
                    String fromParam[]  = fromPair[0].split("=");
                    String fromModule[] = fromPair[1].split("=");

                    String toPair[]   = to.split(",");
                    String toParam[]  = toPair[0].split("=");
                    String toModule[] = toPair[1].split("=");

                    String fromModStr = fromModule[1];
                    String fromParamStr = fromParam[1];

                    String toModStr = toModule[1];
                    String toParamStr = toParam[1];

                    Component fromComp = canMashup.getComponent(fromModStr);
                    Component toComp = canMashup.getComponent(toModStr);

                    if((fromComp != null) && (toComp != null)){
                        Dataflow df = new Dataflow();

                        OutputPort outputPort = new OutputPort();
                        outputPort.setName(fromParamStr);
                        outputPort.setType("");

                        InputPort inputPort = new InputPort();
                        inputPort.setName(toParamStr);
                        inputPort.setType("");

                        df.setFromPort(fromComp, outputPort);
                        df.setToPort(toComp, inputPort);

                        canMashup.addDataflow(df);
                    }

                }
            }
            
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println(ex.getMessage() + ": " + uri.getPath());
            //Logger.getLogger(PipesAdapter.class.getName()).log(Level.SEVERE, null, ex);

        }       
        

    }

    private Component processModule(Map module){
        String type = (String)module.get("type");
        Component comp = new Component();

        if(type.contains("pipe:")){

            comp.setId((String)module.get("id"));
            comp.setIsEmbedding(false);
            comp.setType((String)module.get("type"));
            comp.setName((String)module.get("type"));

            //System.out.println(comp.getId());

        }else{

            comp.setId((String)module.get("id"));
            comp.setIsEmbedding(false);
            comp.setType((String)module.get("type"));
            comp.setName((String)module.get("type"));


            /*****************************
            * Here we load the parameters
            ******************************/

            String compName = comp.getName();

            if(compName.equals("loop")){
                comp.setIsEmbedding(true);
                comp = this.processLoopModule(module, comp);

                return comp;
            }

            //For these component, we do not handle the parameters ("as for now").
            //We need to take into account these components for the component embedding pattern
            if(compName.equals("foreachannotate") || compName.equals("foreach")){
                canMashup.addComponent(comp);
                return comp;
            }

            //In the following we get the conf parameters of the module
            LinkedHashMap conf = (LinkedHashMap)module.get("conf");
            if(conf != null){
                Iterator itConf = conf.keySet().iterator();
                while(itConf.hasNext()){
                     String keyConf = (String)itConf.next();
                     
                    if(conf.get(keyConf) instanceof LinkedHashMap){
                        processLinkedHashMap((LinkedHashMap)conf.get(keyConf), keyConf, comp);
                    }else{
                        processLinkedList((LinkedList)conf.get(keyConf), keyConf, comp);
                    }
                }
            }

        }

        return comp;

        //canMashup.addComponent(comp);
    }

    private void processLinkedHashMap(LinkedHashMap lhm, String parentKey, Component comp){

        try{
        //if what we have is a simple parameter (with type and (value or terminal))
        if((lhm.get("type") != null) && ((lhm.get("value") != null) || (lhm.get("subkey") != null) || (lhm.get("terminal") != null))){
            String ipType = (String)lhm.get("type");
            String ipValue = (String)lhm.get("value");
            String ipTerminal = (String)lhm.get("terminal");
            String ipSubkey = (String)lhm.get("subkey");

            InputParameter ip = new InputParameter();
            ip.setName(parentKey);
            ip.setType(ipType);


            //We treat input parameters with "terminal" in a especial way
            //We set the "value" of the InputParameter with the word "terminal" +
            //the actual value of the "terminal" attribute coming from the JSON file
            //WE NEED TO TAKE THIS INTO ACCOUNT WHEN WE DO CANONICAL-TO-PIPE
            if(ipTerminal != null){
                ip.setValue("terminal:" + ipTerminal);
            }else if (ipValue != null){
                ip.setValue(ipValue);
            }else{
                ip.setValue(ipSubkey);
            }

            if (DatabaseParser.isDataMapped(comp, ip)) ip.setDataMapping(PipesAdapter.TRUE);
            else ip.setDataMapping(PipesAdapter.FALSE);

            comp.addInputParameter(ip);
        }else{
            Iterator itAux = lhm.keySet().iterator();
            while(itAux.hasNext()){
                String keyAux = (String)itAux.next();
                if(lhm.get(keyAux) instanceof LinkedHashMap){
                    processLinkedHashMap((LinkedHashMap)lhm.get(keyAux), parentKey+"."+keyAux, comp);
                }else if(lhm.get(keyAux) instanceof LinkedList){
                    this.processLinkedList((LinkedList)lhm.get(keyAux), parentKey, comp);
                }
            }
        }
        }catch(Exception e){
            System.out.println("COMPONENT NAME: " + comp.getName() + "(" + canMashup.getId() + ")" +
                    "\n" + e.getMessage());
            System.exit(1);
            //throw new Exception("COMPONENT NAME: " + comp.getName() + " - " +e.getMessage());
        }
            
    }

    private void processLinkedList(LinkedList ll, String parentKey, Component comp){
        int ruleCount = -1;
        Iterator itIp = ll.iterator();
        while(itIp.hasNext()){
            Object obj = itIp.next();
            ruleCount++;
            String parName = parentKey + "_" + ruleCount;
            if(obj instanceof LinkedHashMap){
                LinkedHashMap ipLhm = (LinkedHashMap)obj;                
                processLinkedHashMap(ipLhm, parName, comp);
            }else if(obj instanceof LinkedList){
                LinkedList ipLl = (LinkedList)obj;
                processLinkedList(ipLl, parName, comp);
            }

        }
    }

    public Component processLoopModule(Map module, Component comp){
        Map conf = (Map)module.get("conf");

        Map with = (Map)conf.get("with");
        InputParameter ipWith = new InputParameter();
        ipWith.setName("with");
        ipWith.setType((String)with.get("type"));
        ipWith.setValue((String)with.get("value"));
        ipWith.setDataMapping(PipesAdapter.FALSE);
        comp.addInputParameter(ipWith);

        Map emitPart = (Map)conf.get("emit_part");
        InputParameter ipEmitPart = new InputParameter();
        ipEmitPart.setName("emit_part");
        ipEmitPart.setType((String)emitPart.get("type"));
        ipEmitPart.setValue((String)emitPart.get("value"));
        ipEmitPart.setDataMapping(PipesAdapter.FALSE);
        comp.addInputParameter(ipEmitPart);

        Map mode = (Map)conf.get("mode");
        InputParameter ipMode = new InputParameter();
        ipMode.setName("mode");
        ipMode.setType((String)mode.get("type"));
        ipMode.setValue((String)mode.get("value"));
        ipMode.setDataMapping(PipesAdapter.FALSE);
        comp.addInputParameter(ipMode);
        
        Map assignPart = (Map)conf.get("assign_part");
        InputParameter ipAssignPart = new InputParameter();
        ipAssignPart.setName("assign_part");
        ipAssignPart.setType((String)assignPart.get("type"));
        ipAssignPart.setValue((String)assignPart.get("value"));
        ipAssignPart.setDataMapping(PipesAdapter.FALSE);
        comp.addInputParameter(ipAssignPart);

        Map assignTo = (Map)conf.get("assign_to");
        InputParameter ipAssignTo = new InputParameter();
        ipAssignTo.setName("assign_to");
        ipAssignTo.setType((String)assignTo.get("type"));
        ipAssignTo.setValue((String)assignTo.get("value"));
        ipAssignTo.setDataMapping(PipesAdapter.FALSE);
        comp.addInputParameter(ipAssignTo);


        Map embed = (Map)conf.get("embed");
        Map value = (Map)embed.get("value");

        if(value != null){
            Component embeddedComp = processModule(value);
            comp.addEmbeddedComponent(embeddedComp);
        }

        return comp;
        
    }

    public CanonicalMashup getCanonicalMashup() {
        return this.canMashup;
    }

    public List<Component> getComponents() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<Dataflow> getDataflows() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<GlobalParameter> getGlobalParameters() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}
